/**
 * @file XListView.java
 * @package me.maxwin.view
 * @create Mar 18, 2012 6:28:41 PM
 * @author Maxwin
 * @description An ListView support (a) Pull down to refresh, (b) Pull up to load more.
 * 		Implement IXListViewListener, and see stopRefresh() / stopLoadMore().
 */
package com.taptech.doufu.ui.view.pull;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.res.TypedArray;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.DecelerateInterpolator;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.AbsListView.RecyclerListener;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.Scroller;
import android.widget.TextView;

import com.taptech.doufu.util.TTLog;
import com.taptech.doufu.R;
import com.taptech.doufu.constant.Constant;
import com.taptech.doufu.listener.XListRefreshTopListener;

public class XListView extends ListView implements OnScrollListener,RecyclerListener {

	private final String TAG = "XListView";
	
	private float mLastY = -1; // save event y
	private Scroller mScroller; // used for scroll back
	private OnScrollListener mScrollListener; // user's scroll listener

	// the interface to trigger refresh and load more.
	private IXListViewListener mListViewListener;
	private XListRefreshTopListener topListener;
	
	private OnRefreshListener mOnRefreshListener;
	private OnLoadMoreListener mOnLoadMoreListener;

	// -- header view
	private XListViewHeader mHeaderView;
	// header view content, use it to calculate the Header's height. And hide it
	// when disable pull refresh.
	private RelativeLayout mHeaderViewContent;
	private TextView mHeaderTimeView;
	
	private View headerContent;
	
	private int mHeaderViewHeight; // header view's height
	private boolean mEnablePullRefresh = true;
	private boolean mPullRefreshing = false; // is refreashing.

	// -- footer view
	private XListViewFooter mFooterView;
	private boolean mEnableLoadMore;
	private boolean mPullLoading;
	private boolean mIsFooterReady = false;
	
	// total list items, used to detect is at the bottom of listview.
	private int mTotalItemCount;

	// for mScroller, scroll back from header or footer.
	private int mScrollBack;
	private final static int SCROLLBACK_HEADER = 0;
	private final static int SCROLLBACK_FOOTER = 1;

	private final static int SCROLL_DURATION = 400; // scroll back duration
	private final static int PULL_LOAD_MORE_DELTA = 50; // when pull up >= 50px
														// at bottom, trigger
														// load more.
	private final static float OFFSET_RADIO = 1.8f; // support iOS like pull
													// feature.
	
	// xlist:xRefreshMode
	private final int X_REFRESH_MODE_DEFAULT = -1;
	private final int X_REFRESH_MODE_NONE = 0;
	private final int X_REFRESH_MODE_PULL_TO_REFRESH = 1;
	private final int X_REFRESH_MODE_LOAD_MORE = 2;
	private final int X_REFRESH_MODE_BOTH = 3;
	
	//
	private boolean mFirstTimeInit = true;
	
	
	private SharedPreferences mSettingsRefreshTime;
	private String mRefreshTimeTag;
	/**
	 * @param context
	 */
	public XListView(Context context) {
		super(context);
		initWithContext(context);
		
		initWithAttrs(context, null);
	}

	public XListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initWithContext(context);
		
		initWithAttrs(context, attrs);
		
	}

	public XListView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		initWithContext(context);
		
		initWithAttrs(context, attrs);
		
	}
	
	private void initWithContext(Context context) {
		setRecyclerListener(this);
		mSettingsRefreshTime = context.getSharedPreferences(Constant.SettingsRefreshTime.NAME, Context.MODE_PRIVATE);
		
		mRefreshTimeTag = null;
		
		mScroller = new Scroller(context, new DecelerateInterpolator());
		// XListView need the scroll event, and it will dispatch the event to
		// user's listener (as a proxy).
		super.setOnScrollListener(this);

		// init header view
		mHeaderView = new XListViewHeader(context);
		mHeaderViewContent = (RelativeLayout) mHeaderView
				.findViewById(R.id.xlistview_header_content);
		headerContent = mHeaderView.findViewById(R.id.xlistview_header_text);
		mHeaderTimeView = (TextView) mHeaderView
				.findViewById(R.id.xlistview_header_time);
		addHeaderView(mHeaderView); // modify by zhangyanhui

		// init footer view
		mFooterView = new XListViewFooter(context);

		// init header height
		mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(
				new OnGlobalLayoutListener() {
					@Override
					public void onGlobalLayout() {
						mHeaderViewHeight = mHeaderViewContent.getHeight();
						getViewTreeObserver()
								.removeGlobalOnLayoutListener(this);
					}
				});
		
		this.mFirstTimeInit = true;
	}

	
	private void initWithAttrs(Context context, AttributeSet attrs) {
		
		
		// now style everything!
		TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.XListView);
						
		int mode = ta.getInt(R.styleable.XListView_xRefreshMode, X_REFRESH_MODE_DEFAULT);
						
		TTLog.e("XListView", mode + "");
		
		switch (mode) {
		case X_REFRESH_MODE_NONE:
			setPullRefreshEnable(false);
			setLoadMoreEnable(false);
			break;
			
		case X_REFRESH_MODE_PULL_TO_REFRESH:
			setPullRefreshEnable(true);
			setLoadMoreEnable(false);
			break;
			
		case X_REFRESH_MODE_LOAD_MORE:
			setPullRefreshEnable(false);
			setLoadMoreEnable(true);
			break;
			
		case X_REFRESH_MODE_BOTH:
			setPullRefreshEnable(true);
			setLoadMoreEnable(true);
			break;

		default:
			setPullRefreshEnable(false);
			setLoadMoreEnable(false);
			break;
		}
		
		
	}
	
	@Override
	public void setAdapter(ListAdapter adapter) {
		// make sure XListViewFooter is the last footer view, and only add once.
		if (mIsFooterReady == false) {
			mIsFooterReady = true;
			addFooterView(mFooterView); // modify by zhangyanhui
		}
		super.setAdapter(adapter);
	}

	/**
	 * enable or disable pull down refresh feature.
	 * 
	 * @param enable
	 */
	public void setPullRefreshEnable(boolean enable) {
		mEnablePullRefresh = enable;
		if (!mEnablePullRefresh) { // disable, hide the content
			mHeaderViewContent.setVisibility(View.INVISIBLE);
		} else {
			mHeaderViewContent.setVisibility(View.VISIBLE);
		}
	}

	/**
	 * enable or disable pull up load more feature.
	 * 
	 * @param enable
	 */
	public void setLoadMoreEnable(boolean enable) {
		mEnableLoadMore = enable;
		if (!mEnableLoadMore) {
			mFooterView.hide();
			mFooterView.setOnClickListener(null);
		} else {
			mPullLoading = false;
			mFooterView.show();
			mFooterView.setState(XListViewFooter.STATE_NORMAL);
			// both "pull up" and "click" will invoke load more.
			mFooterView.setOnClickListener(new OnClickListener() {
				@Override
				public void onClick(View v) {
					startLoadMore();
				}
			});
		}
	}

	/**
	 * stop refresh, reset header view.
	 */
	public void stopRefresh() {
		if (mPullRefreshing == true) {
			mPullRefreshing = false;
			TTLog.e(TAG, "stopRefresh");
			resetHeaderHeight();
		}
	}

	/**
	 * stop load more, reset footer view.
	 */
	public void stopLoadMore() {
		if (mPullLoading == true) {
			mPullLoading = false;
			mFooterView.setState(XListViewFooter.STATE_NORMAL);
		}
	}

	/**
	 * set last refresh time
	 * 
	 * @param time
	 */
	public void setRefreshTime(String time) {
		mHeaderTimeView.setText(time);
		
		if (mSettingsRefreshTime != null && mRefreshTimeTag != null) {
			Editor editor = mSettingsRefreshTime.edit();
			
			editor.putString(mRefreshTimeTag, time);
			
			editor.commit();
		}
	}
	
	
	//
	public void setRefreshTimeTag(String tag) {
		this.mRefreshTimeTag = tag;
		
		//
		String time = mSettingsRefreshTime.getString(tag, null);
		
		if (time != null) {
			setRefreshTime(time);
		}
	}
	
	public String getRefreshTimeTag() {
		return this.mRefreshTimeTag;
	}

	private void invokeOnScrolling() {
		if (mScrollListener instanceof OnXScrollListener) {
			OnXScrollListener l = (OnXScrollListener) mScrollListener;
			l.onXScrolling(this);
		}
	}

	private void updateHeaderHeight(float delta) {	
		if (mEnablePullRefresh && !mPullRefreshing) { //
			mHeaderView.setVisiableHeight((int) delta
					+ mHeaderView.getVisiableHeight());	
			if (mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
				mHeaderView.setState(XListViewHeader.STATE_READY);
			} else {
				mHeaderView.setState(XListViewHeader.STATE_NORMAL);
			}
		}
		setSelection(0); // scroll to top each time
	}

	/**
	 * reset header view's height.
	 */
	private void resetHeaderHeight() {
		int height = mFirstTimeInit ? mHeaderView.getRealHeight() :mHeaderView.getVisiableHeight();
		
		mFirstTimeInit = false;
		
		if (height == 0) {// not visible.
			TTLog.e(TAG, "height == 0");
			return;
		}
		// refreshing and header isn't shown fully. do nothing.
		if (mPullRefreshing && height <= mHeaderViewHeight) {
			TTLog.e(TAG, "mPullRefreshing && height <= mHeaderViewHeight");
			return;
		}
		int finalHeight = 0; // default: scroll back to dismiss header.
		// is refreshing, just scroll back to show all the header.
		if (mPullRefreshing && height > mHeaderViewHeight) {
			finalHeight = mHeaderViewHeight;
		}
		mScrollBack = SCROLLBACK_HEADER;
		
		TTLog.e(TAG, "start scroll");
		
		mScroller.startScroll(0, height, 0, finalHeight - height,
				SCROLL_DURATION);
		// trigger computeScroll
		invalidate();
	}

	private void updateFooterHeight(float delta) {
		int height = mFooterView.getBottomMargin() + (int) delta;
		if (mEnableLoadMore && !mPullLoading) {
			if (height > PULL_LOAD_MORE_DELTA) { // height enough to invoke load
													// more.
				mFooterView.setState(XListViewFooter.STATE_READY);
			} else {
				mFooterView.setState(XListViewFooter.STATE_NORMAL);
			}
		}
		mFooterView.setBottomMargin(height);

//		setSelection(mTotalItemCount - 1); // scroll to bottom
	}

	private void resetFooterHeight() {
		int bottomMargin = mFooterView.getBottomMargin();
		if (bottomMargin > 0) {
			mScrollBack = SCROLLBACK_FOOTER;
			mScroller.startScroll(0, bottomMargin, 0, -bottomMargin,
					SCROLL_DURATION);
			invalidate();
		}
	}
	
	public void startRefresh() {
		if (!mEnablePullRefresh) {
			return;
		}
		mPullRefreshing = true;
		mHeaderView.setVisible();
		mHeaderView.setState(XListViewHeader.STATE_REFRESHING);
		setSelection(0);
	}

	private void startLoadMore() {
		mPullLoading = true;
		mFooterView.setState(XListViewFooter.STATE_LOADING);
		if (mListViewListener != null) {
			mListViewListener.onLoadMore();
		}
		
		if (mOnLoadMoreListener != null) {
			mOnLoadMoreListener.onLoadMore();
		}				
	}
	
	public boolean isRefreshing() {
		return this.mPullRefreshing;
	}
	
	public boolean isLoadingMore() {
		return this.mPullLoading;
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		
		if(!isEnabled())
			return false;
		
		if (mLastY == -1) {
			mLastY = ev.getRawY();
		}

		switch (ev.getAction()) {
		case MotionEvent.ACTION_DOWN:
			mLastY = ev.getRawY();
			break;
		case MotionEvent.ACTION_MOVE:			
			final float deltaY = ev.getRawY() - mLastY;
			if(topListener!=null)
			{
				topListener.move();
			}
			
			mLastY = ev.getRawY();
			
			if (deltaY < 0 && topListener!=null) {		
					topListener.hideTop();
			}
			
			if(getFirstVisiblePosition() == 0 && deltaY > 0 && topListener!=null)
			{
				topListener.showTop();
			}			
			
			
			if (getFirstVisiblePosition() == 0
					&& (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
				// the first item is showing, header has shown or pull down.
					updateHeaderHeight(deltaY / OFFSET_RADIO);
					invokeOnScrolling();						
			}
			else if (getLastVisiblePosition() == mTotalItemCount - 1
					&& (mFooterView.getBottomMargin() > 0 || deltaY < 0)) {
				// last item, already pulled up or want to pull up.
				updateFooterHeight(-deltaY / OFFSET_RADIO);
			}
			break;
		case MotionEvent.ACTION_UP:
			stopRefresh();
		default:
			mLastY = -1; // reset
			if (getFirstVisiblePosition() == 0) {
				// invoke refresh
				
//				if(topListener!=null)
//				{
//					topListener.showTop();
//				}					
//				else 
					if (mEnablePullRefresh
						&& mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
					mPullRefreshing = true;
					
					
						mHeaderView.setState(XListViewHeader.STATE_REFRESHING);
						if (mListViewListener != null) {
							mListViewListener.onRefresh();
						}
						
						if (mOnRefreshListener != null) {
							//
							setDataState(XListViewFooter.HAS_MORE_DATA,true);
							mOnRefreshListener.onRefresh();
						}
					
				}
				resetHeaderHeight();
			} else if (getLastVisiblePosition() == mTotalItemCount - 1) {
				// invoke load more.
				if (mEnableLoadMore
						&& mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA) {
					startLoadMore();
				}
				resetFooterHeight();
			}
			break;
		}
		return super.onTouchEvent(ev);
	}

	@Override
	public void computeScroll() {
		if (mScroller.computeScrollOffset()) {
			if (mScrollBack == SCROLLBACK_HEADER) {
				mHeaderView.setVisiableHeight(mScroller.getCurrY());
			} else {
				mFooterView.setBottomMargin(mScroller.getCurrY());
			}
			postInvalidate();
			invokeOnScrolling();
		}
		super.computeScroll();
	}

	@Override
	public void setOnScrollListener(OnScrollListener l) {
		mScrollListener = l;
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if (mScrollListener != null) {
			mScrollListener.onScrollStateChanged(view, scrollState);
		}
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		// send to user's listener
		mTotalItemCount = totalItemCount;
		if (mScrollListener != null) {
			mScrollListener.onScroll(view, firstVisibleItem, visibleItemCount,
					totalItemCount);
		}
	}

	public void setXListViewListener(IXListViewListener l) {
		mListViewListener = l;
	}
	
	/**
	 * if the list has no more data , should useless this to tell the footer view
	 * 
	 * attention: if user use the onRefresh() method, please reset the dataState
	 * 
	 * */
	public void setDataState(int dataState,boolean isFoot) {
		if (mFooterView != null) {
			mFooterView.setDataState(dataState,isFoot);
		}
		
	}
	
	

	/**
	 * you can listen ListView.OnScrollListener or this one. it will invoke
	 * onXScrolling when header/footer scroll back.
	 */
	public interface OnXScrollListener extends OnScrollListener {
		public void onXScrolling(View view);
	}

	/**
	 * implements this interface to get refresh/load more event.
	 */
	public interface IXListViewListener {
		public void onRefresh();

		public void onLoadMore();
	}
	
	
	///
	public void onRefreshComplete() {
		stopRefresh();
		
		
		// need to update time here
	}
	
	public void onLoadMoreComplete() {
		stopLoadMore();
	}
	
	
	//
	public void setOnRefreshListener(OnRefreshListener listener) {
		this.mOnRefreshListener = listener;
	}
	
	public void setOnLoadMoreListener(OnLoadMoreListener listener) {
		this.mOnLoadMoreListener = listener;
	}
	
	
	/**
	 * Interface definition for a callback to be invoked when list should be
	 * refreshed.
	 */
	public interface OnRefreshListener {
		/**
		 * Called when the list should be refreshed.
		 * <p>
		 * A call to {@link PullToRefreshListView #onRefreshComplete()} is
		 * expected to indicate that the refresh has completed.
		 */
		public void onRefresh();
	}
	
	/**
	 * Interface definition for a callback to be invoked when list reaches the
	 * last item (the user load more items in the list)
	 */
	public interface OnLoadMoreListener {
		/**
		 * Called when the list reaches the last item (the last item is visible
		 * to the user) A call to
		 * {@link PullAndLoadListView #onLoadMoreComplete()} is expected to
		 * indicate that the loadmore has completed.
		 */
		public void onLoadMore();
	}


	public XListRefreshTopListener getTopListener() {
		return topListener;
	}

	public void setTopListener(XListRefreshTopListener topListener) {
		this.topListener = topListener;
		headerContent.setVisibility(View.GONE);		
	}



	public int getmHeaderViewHeight() {
		return mHeaderViewHeight;
	}

	public void setmHeaderViewHeight(int mHeaderViewHeight) {
		this.mHeaderViewHeight = mHeaderViewHeight;
	}

	@Override
	public void onMovedToScrapHeap(View view) {

	}
	
	@Override
	protected void layoutChildren() {
		super.layoutChildren();
		setEnabled(true);
	}

	public XListViewHeader getmHeaderView() {
		return mHeaderView;
	}

	public void setmHeaderView(XListViewHeader mHeaderView) {
		this.mHeaderView = mHeaderView;
	}


}
